﻿/*
 * 
 * Sprite possédant :
 * 
 * une Armature
 * une pose par défaut
 * ( des poignées ? des joints physiques ? des contraintes ? )
 * 
 * les IKBone nécessaires sont :
 * 
 * bone_mainD
 * bone_piedD
 * bone_mainG
 * bone_piedG
 * epine
 * 
 * Le MovieClip Tete contenant les Yeux
 * 
 * Gestion des armatures :
 * 
 * Il ne peut pas exister 2 instances avec la même armature : la dernière écrase les précédentes.
 * à cause du IKManager.getArmatureByName( "name" );
 * 
 * */
package bipede.membres
{
	import drawer.events.DrawEvent;
	
	import fab.geom.Drawer;
	import fab.geom.Circle;
	import fab.geom.Line;
	import fab.geom.Segment;
	import fab.Utils;
	
	import events.MarcheEvent;
	import events.MoveEvent;
	import events.PersonEvent;
	import events.PhysicEvent;

	import bipede.controleurs.Animateur;
	import bipede.animation.Pose;
	
	import physic.Liaison;
	import physic.Contrainte;
	import physic.Engine;
	import physic.SoftBody;
	import physic.Obstacle;
	
	import clips.Poignee_mc;
	import clips.Poignee;
	
	import fl.ik.*;

	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.geom.Matrix;	
	import flash.geom.Rectangle;
	
	import flash.utils.getQualifiedClassName;	
	import flash.utils.Timer;
	import flash.utils.Dictionary;
	
	public class Squelette extends Sprite 
	{
		public var tree:IKArmature;
		public var testIKjoints:Vector.<PhysicJoint> = new Vector.<PhysicJoint>();
		public var pieds:Vector.<Pied> = new Vector.<Pied>();
		public var lesBras:Vector.<Bras> = new Vector.<Bras>();
		//public var contraintes:Vector.<Contrainte> = new Vector.<Contrainte>();
		public var contraintes:Object = new Object();
		public var physic_bones:Object = new Object();
		//private var poignees:Vector.<Poignee> = new Vector.<Poignee>();
		public var data:Dictionary = new Dictionary();
		private var _pose:Pose;
		private var _target_pose:Pose;
		
		public var default_pose:Pose;
		
		public var initStatus:Object;
		
		
		private var clipBone:Sprite;
		private var ikMover:IKMover;
		private var bones_names:Array;
		private var ikMovers:Vector.<IKMover> = new Vector.<IKMover>();
		
		//public var animateurs:Vector.<Animateur> = new Vector.<Animateur>();
		public var animateurs:Dictionary = new Dictionary();
		
		public var main_gauche:Bras;
		public var main_droite:Bras;
		public var pied_gauche:Pied;
		public var pied_droit:Pied;
		public var current_pied:Pied;
		public var autre_pied:Pied;
		private var _start_pose:Pose;
		
		private var marche_compteur:Timer;

		private var anim_compteur:Timer;
		private var anim_increment:Number;
		
		public var physics:SoftBody;
		
		public var _rectangle_pied_gauche:Rectangle;
		
		
		public function init():void 
		{
			main_gauche = create_main( "bone_mainG" );
			main_droite = create_main( "bone_mainD" );
			
			pied_gauche = create_pied( "bone_piedG" );
			pied_droit = create_pied( "bone_piedD" );
			
			current_pied = pied_gauche;
			autre_pied = pied_droit;
			
			anim_compteur = new Timer( 40, 10 );
			anim_compteur.addEventListener( TimerEvent.TIMER, _tick );
			anim_compteur.addEventListener( TimerEvent.TIMER_COMPLETE, _fin_tick );

			//IKManager.trackIKObject( dummy_mainD, true );
			//create_poignee_mc( "dummy_mainD" );
			
			parse_IKJoint_tree( register_physic_bone, tree.rootJoint );

			//addEventListener( MoveEvent.MOVING, moving );// passage dans physics
		};		

		public function Squelette() 
		{
			addEventListener(Event.FRAME_CONSTRUCTED, frameConstructedHandler);
		}		
		private function frameConstructedHandler(event:Event):void
		{
			removeEventListener(Event.FRAME_CONSTRUCTED, frameConstructedHandler);

			tree = IKManager.getArmatureByName( "Armature_" + getQualifiedClassName( this ) );

			tree.registerElements( this );
			default_pose = pose;
			
			// trace de armature
			
			parse_IKJoint_tree( traceArmature, tree.rootJoint );
			
			initStatus = { scaleX:scaleX, scaleY:scaleY };
			
			init();// to override
			
			dispatchEvent( new PersonEvent( PersonEvent.ARMATURE_READY ) );
			dispatchEvent( new PersonEvent( PersonEvent.ANIMATIONS_READY ) );
		}
		
		
		public function create_contrainte( p:Point, mobileBoneNom:String, attachBoneNom:String ):Contrainte
		{
			// TODO
			release_joint_constraint( mobileBoneNom, 4 );
			
			var mobileBone:IKBone = tree.getBoneByName( mobileBoneNom );
			//var tailJoint:IKJoint = mobileBone.tailJoint;	

			var attachBone:IKBone = tree.getBoneByName( attachBoneNom );
			//var attachTailJoint:IKJoint = attachBone.tailJoint;	
			
			var c:Contrainte = new Contrainte( this, p, mobileBone, attachBone );
			contraintes[ mobileBone.tailJoint.name ] = c;
			return c;
		}
		public function create_physic_joint( nom:String ):void
		{
			var bone:IKBone = tree.getBoneByName( nom );
			var tailJoint:IKJoint = bone.tailJoint;
			testIKjoints.push( new PhysicJoint( tailJoint ) );
		}		
		public function create_pied( nom:String ):Pied
		{
			var bone:IKBone = tree.getBoneByName( nom );
			//var tailJoint:IKJoint = bone.tailJoint;
			var pied:Pied = new Pied( this, bone );
			pieds.push( pied );
			return pied;
		}
		public function get tete():*
		{
			var bone:IKBone = tree.getBoneByName( "epine" );
			//trace(bone);
			var tailJoint:IKJoint = bone.tailJoint;
			//trace(tailJoint);
			trace(tailJoint.name);
			return ( tailJoint );
		}
		public function create_main( nom:String ):Bras
		{
			var bone:IKBone = tree.getBoneByName( nom );
			//var tailJoint:IKJoint = bone.tailJoint;
			var bras:Bras = new Bras( this, bone );
			lesBras.push( bras );
			trace("create_main", nom, this.name, bras, bone);
			return bras;
		}		
		public function create_poignee_bone( nom:String, x:int=0, y:int=0 ):Poignee
		{
			var bone:IKBone = tree.getBoneByName( nom );
			var tailJoint:IKJoint = bone.tailJoint;
			var position:Point = tailJoint.position;
			
			var ikMover:IKMover = new IKMover( tailJoint, tailJoint.position );
			
			var j:Poignee = new Poignee( ikMover, tailJoint );
			//stage.
			addChild( j );
			j.x = position.x;
			j.y = position.y;
			return j;
		}
		public function create_poignee_mc( nom:String, x:int=0, y:int=0 ):void
		{			
			var j:Poignee_mc = new Poignee_mc( this[ nom ] );
			addChild( j );
			j.x = this[ nom ].x;
			j.y = this[ nom ].y;
		}	
		

		public function set orientation( vers_la_droite:Boolean ):void
		{
			scaleX = vers_la_droite?initStatus.scaleX: - initStatus.scaleX;
		}	
		/*
		 * renvoie true si vers la droite
		 * */
		public function get orientation():Boolean
		{
			return scaleX > 0;
		}			
		public function get pose():Pose
		{
			_pose = new Pose();//<pose></pose>;
			parse_IKJoint_tree( getPoseJoint, tree.rootJoint );
			return _pose;
		}
		public function set pose(p:Pose):void
		{
			_target_pose = p;
			parse_IKJoint_tree( setPoseJoint, tree.rootJoint );
		}		
		public function set place(p:Point):void
		{
			//x = int( p.x );// foire les petites anim comme bras_ballants
			//y = int( p.y );
			x = ( p.x );
			y = ( p.y );			
		}
		public function get place():Point
		{
			return new Point( x, y );
		}
		
		// mapping functions
		
		public function getPoseJoint( nextJoint:* ):void
		{
			_pose.append( nextJoint.name, nextJoint.position );
		}
		/*
		public function setPoseJoint( nextJoint:* ):void
		{
			var position:Point = nextJoint.position;
			var target:Point = _target_pose.getPosition( nextJoint.name );
			
			var ikMover:IKMover = new IKMover( nextJoint, position );
			ikMover.limitByIteration = true;
			ikMover.iterationLimit = 6;
			ikMover.moveTo( target );
		}	
		*/
		public function traceArmature( obj:* ):void
		{
			trace( "\tJoint " + obj.name + "\t\t\tsur Bone " + obj.bone.name + "\t\t\t yTranslationMin " + obj.yTranslationMin + "\t\t\t yTranslation " + obj.yTranslation );
		}
		public function register_physic_bone( obj:* ):void
		{
			physic_bones[ obj.bone.name ] = new PhysicBone( obj.bone );
		}		

		// pose de l'armature :
		// 2 méthodes : tous les joints, ou les joints à poignee ? ou les bones ?
		
		public function parse_IKJoint_tree( recursiveFunc:Function, joint:IKJoint ):void
		{
			recursiveFunc( joint );
			for ( var i:int = 0; i < joint.numChildren; i++ )
			{
				var childJoint = joint.getChildAt( i );
				//if ( childJoint.numChildren > 0 ) 
				parse_IKJoint_tree( recursiveFunc, childJoint );
			}
		}	
		public function distance_au_support( support:Obstacle ):int
		{
			return support.plateforme.distance( place );
		}	
		public function release_joint_constraint( nom:String, steps:int=2 ):void
		{
			var step:int = 0;
			var bone:IKBone = tree.getBoneByName( nom );
			var currentJoint:IKJoint = bone.tailJoint;
			while( currentJoint != tree.rootJoint || step < steps )
			{
				currentJoint.rotationConstrained = false;
				step++;
				currentJoint = currentJoint.parent;
			}
		}	
		public function joint2root_distance_px( nom:String ):int
		{
			var bone:IKBone = tree.getBoneByName( nom );
			var d:int = physic_bones[ bone.name ].longueur;
			var currentJoint:IKJoint = bone.tailJoint;
			while( currentJoint != tree.rootJoint )
			{
				currentJoint = currentJoint.parent;
				d += physic_bones[ currentJoint.bone.name ].longueur;
			}
			return d;
		}		
		
		
		// ------------------------ from Person :
		
	
		public function record():Pose
		{
			return pose;
		}
		public function set_default_pose( event:MouseEvent=null ):void
		{
			pose = default_pose;
		}	
		public function register_animateur( anim:Animateur ):void
		{
			trace("register_animateur " + anim.type);
			animateurs[ anim.type ] = anim;
		}			
		public function unregister_animateur( anim:Animateur ):void
		{
			trace("UNregister_animateur " + anim.type);
			delete animateurs[ anim.type ];
			//animateurs[ anim.type ] = null;
			trace("animateurs length " + animateurs.length);
		}
		public function applique():void
		{
			
			
		}	
		public function startmoving( event:MoveEvent ):void
		{
			//Engine.stop();
			
			//default_pose = pose;
			set_default_pose();
			//trace("startmoving\r" + default_pose);
			
			for each ( var anim:Animateur in animateurs )
			{
				anim.stop();
				//anim = null;
				//unregister_animateur( anim );
			}
		}
		public function endmoving( event:MoveEvent ):void
		{
			//physics.vitesse = vitesse;
			trace(physics.vitesse);
			//Engine.start();
			//pose = default_pose;// tweener !
			//trace("endmoving " + default_pose);
			
			//positionne( default_pose, 4 );
			
			
		}
		public function _physic_update( event:PhysicEvent ):void
		{
			//positionne( pose );// .translation( new Point( - physics.vitesse.x, - physics.vitesse.y ) ) );
		}		
		/*
		 * MOVING est subie
		 * */
		public function moving(event:MoveEvent = null):void
		{
			var cible:Point = event.point;

			for each ( var contrainte:Contrainte in contraintes ) cible = contrainte.correction( cible );
			/*
			for each ( var pied:Pied in pieds )
			{			
				if ( pied.support != null )//pied_droit ?
				{
					if ( pied.support.plateforme.distance( cible ) < distance_support )
					{
						var p:Point = pied.support.plateforme.projection( cible );
						Drawer.drawPoint( Main.drawings.graphics, p );
						var cercle:Circle = new Circle( p, distance_support );
						cible = cercle.projection( cible, false );	
					}
					else {
						pied.remove_support();
					}
				}
			}
			*/
			var vitesse:Point = cible.subtract( place );

			place = cible;
			
			if ( vitesse.length > 4 ) 
			positionne( default_pose );// pose.translation( new Point( vitesse.x, vitesse.y ) ) );
			
			if ( physics != null ) physics.vitesse = vitesse;
		}
		/* 
		 * renvoie un rectangle de test pour la pose du pied dans le systeme de Engine.espace
		 * */
		public function get rectangle_pied_gauche():Rectangle
		{
			if ( !orientation ) data.rectangle_pied_gauche.x = 170;
			else data.rectangle_pied_gauche.x = 20;
			
			var origine:Point = Utils.PointLocalTransformation( data.rectangle_pied_gauche.topLeft, this, Engine.espace );
			// ( int ) pour les tests d'instersectRectangle
			return new Rectangle( int(origine.x), int(origine.y), data.rectangle_pied_gauche.width, data.rectangle_pied_gauche.height );
		}
		public function get rectangle_pied_droit():Rectangle
		{
			if ( !orientation ) data.rectangle_pied_gauche.x = 170;
			else data.rectangle_pied_gauche.x = 20;
			var rect:Rectangle = data.rectangle_pied_gauche.clone();
			rect.offset( -45, 0 );
			var origine:Point = Utils.PointLocalTransformation( rect.topLeft, this, Engine.espace );
			// ( int ) pour les tests d'instersectRectangle
			return new Rectangle( int(origine.x), int(origine.y), data.rectangle_pied_gauche.width, data.rectangle_pied_gauche.height );
		}		
		
		// à placer côté Person ??? :
		
		public function findSupport(event:MarcheEvent):void
		{
			var rectangle:Rectangle = event.dynamic_pied_gauche?rectangle_pied_gauche:rectangle_pied_droit; 
			var intersect_array:Array = new Array();
			var intersect:Segment;
			
			dispatchEvent( new DrawEvent( DrawEvent.DRAWRECTANGLE, rectangle, 0xFF0000 ) );
			
			// recherche des Segments de plateformes d'obstacle inclus dans un rectangle
			
			for each ( var obstacle:Obstacle in Engine.obstacles )
			{
				//if ( obstacle.display.hitTestPoint( p.x, p.y, true ) )
				//{
					//Drawer.drawRectangle( Main.drawings.graphics, rectangle );	
					var plateforme:Segment = obstacle.plateforme;
					//trace("findSupport ? "+obstacle);
					//Drawer.drawSegment( Main.drawings.graphics, plateforme.debut, plateforme.fin );
					intersect = plateforme.instersectRectangle( rectangle );
					if ( intersect != null )
					{
						intersect_array.push( { intersect:intersect, obstacle:obstacle, xmax:orientation?intersect.rectangle.right:intersect.rectangle.left, ymin:intersect.rectangle.top } );
					}
				//}
			}
			
			// classement des segments par leur xmax et envoie du premier
			
			if ( intersect_array.length > 0 )
			{
				intersect_array.sortOn( "xmax", (orientation?Array.DESCENDING:Array.NUMERIC) | Array.NUMERIC );
				//intersect_array.sortOn( "ymin", Array.DESCENDING | Array.NUMERIC );
				
				//Main.drawings.graphics.lineStyle( 0, 0x00FF00 );
				//Drawer.drawSegment( Main.drawings.graphics, intersect.debut, intersect.fin );						
				dispatchEvent( new MarcheEvent( MarcheEvent.LOCALISE_SUPPORT, intersect_array[ 0 ].intersect, intersect_array[ 0 ].obstacle ) );			
			}
			else {
				dispatchEvent( new MarcheEvent( MarcheEvent.NO_SUPPORT ) );	
				trace(" pas de support" );
			}
		}
		public function repose(event:MoveEvent):void
		{
			stand(event);
			support = event.support;
		}
		public function get support():Obstacle
		{
			if ( pied_droit.support != null ) return pied_droit.support;
			if ( pied_gauche.support != null ) return pied_gauche.support;
			return null;
		}		
		public function set support(o:Obstacle):void
		{
			pied_droit.support = o;
			pied_gauche.support = o;
		}
		public function stand(event:MoveEvent):void
		{
			//trace("stand " + event );
			if ( event.support == null )
			{
				trace( "il faut un support pour se stand." );
				return;
			}
			if ( event.point != null ) place = event.point;
			stayOnSupport( event.support, true, true );	
		}	
		public function stayOnSupport(obstacle:Obstacle, attract:Boolean = true, toujours:Boolean = false ):void
		{
			place = new Circle( support.plateforme.projection( place ), data.distance_support ).projection( place, attract, toujours );
		}
		public function move(event:MoveEvent):void
		{
			var cible:Point = (event.point as Point).clone();
			var vitesse:Point;
			
			if ( event.increment )
			{
				vitesse = cible;
				place = place.add( cible );
			}
			else {
				vitesse = cible.subtract( place );
				place = cible;
			}
			if ( event.support != null ) stayOnSupport( event.support );

			//positionne( pose.translation( new Point( -vitesse.x, -vitesse.y ) ) );
			if ( physics != null ) physics.vitesse = vitesse;			
		}				
		public function atterrit(event:MoveEvent):void
		{
			if ( animateurs.saut.running ) return;
			
			// event.point contient un tableau d'object { intersect:Point intersection, obstacle:Obstacle touché }
			var hitted_obstacles:Array = event.point;
			
			var hitted_obstacle:Obstacle = hitted_obstacles[ 0 ].obstacle;
			support = hitted_obstacle;
			
			// point sur la plateforme, que croise le vecteur deplacement de Person augmenté de data.distance_support_max
			var hitted_point:Point = hitted_obstacles[ 0 ].intersect;
			
			// calcul du point bas du Person p/r au support _bloc
			var cercle:Circle = new Circle( hitted_point, data.distance_support );
			var cible:Point = cercle.projection( place, true, true );	
			
			pied_droit.support = support;
			pied_gauche.support = support;
			
			//Engine.unRegisterSoftBody( this );// fait, du côté de Engine
			
			var atterrissage:Animateur = new Animateur( this );
			atterrissage.action( "atterrissage" );
			atterrissage.configure( { 
				support:support, 
				sujet:this,
				place:place,
				cible:cible,
				vitesse:.2
				} );
			atterrissage.start();
		}					
		public function decolle(event:MoveEvent):void
		{
			trace("decolle");
			support = null;
			physics = Engine.registerSoftBody( this );
			physics.vitesse = event.point.vitesse_sujet;
			if ( !Engine.started ) Engine.getInstance().start();
		}
		
		// mapping functions
		
		//override 
		public function setPoseJoint( nextJoint:* ):void
		{
			var position:Point = nextJoint.position;
			
			var target:Point = _target_pose.getPosition( nextJoint.name );
			var startposition:Point = _start_pose.getPosition( nextJoint.name );

			var ikMover:IKMover = new IKMover( nextJoint, position );
			ikMover.limitByIteration = true;
			ikMover.iterationLimit = 6;
			
			var cible:Point = Point.interpolate( target, startposition, anim_increment );
			
			// contraintes
			var contrainte:Contrainte = contraintes[ nextJoint.name ]
			if ( contrainte != null ) cible = globalToLocal( contrainte );

			ikMover.moveTo( cible );
		}
		public function positionne( newpose:Pose, steps:int=0 ):void
		{
			//trace( "\t///////////////////////// positionne " + name );
			
			_start_pose = pose;
			_target_pose = newpose;
			
			anim_compteur.reset();
			if ( steps == 0 )
			{
				anim_increment = 1;
				parse_IKJoint_tree( setPoseJoint, tree.rootJoint );
			}
			else {
				anim_compteur.repeatCount = steps;
				anim_compteur.start();
			}
			//trace( "\t///////////////////////// ------------" );
		}
		
		// pour temporiser avant nouveau frame :
		
		private function _fin_tick( event:TimerEvent ):void
		{
			dispatchEvent( new MoveEvent( MoveEvent.FINFRAME ) );
		}		
		private function _tick( event:TimerEvent ):void
		{
			anim_increment = anim_compteur.currentCount / anim_compteur.repeatCount;
			
			trace( ">increment> " + anim_increment );
			
			parse_IKJoint_tree( setPoseJoint, tree.rootJoint );
		}		
	}
}